/*
 *
 *      Copyright (c) 2018-2025, lengleng All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the pig4cloud.com developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: lengleng (wangiegie@gmail.com)
 *
 */
package top.blackr.folivora.common.log.aspect;

import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.utils.StringUtil;
import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import top.blackr.folivora.common.log.annotation.SysLog;
import top.blackr.folivora.common.log.util.LogTypeEnum;
import top.blackr.folivora.common.log.util.SysLogUtils;

import java.lang.reflect.Method;

/**
 * 操作日志使用spring event异步入库
 * @author BlackR
 */
@Slf4j
@Aspect
@Component
@AllArgsConstructor
public class SysLogAspect {

	private final SysLogService logService;
	private final Ip2regionSearcher ip2regionSearcher;

	@SneakyThrows
	@Around("@annotation(sysLog)")
	public Object around(ProceedingJoinPoint point, SysLog sysLog) {
		String strClassName = point.getTarget().getClass().getName();
		String strMethodName = point.getSignature().getName();
		log.debug("@SysLog——[类名]:{},[方法]:{}", strClassName, strMethodName);

		String value = sysLog.value();
		String expression = sysLog.expression();
		if (StrUtil.isNotBlank(expression)) {
			// 解析SPEL
			MethodSignature signature = (MethodSignature) point.getSignature();
			EvaluationContext context = getContext(point.getArgs(), signature.getMethod());
			try {
				value = getValue(context, expression, String.class);
			} catch (Exception e) {
				log.error("@SysLog 解析SPEL {} 异常", expression);
			}
		}
		// 获取日志信息
		top.blackr.folivora.common.log.aspect.SysLog log = SysLogUtils.getSysLog(value);
		// 计时执行时间
		Long startTime = System.currentTimeMillis();
		Object obj;
		// 也可以在@AfterThrowing处拦截异常信息
		try {
			// 执行方法
			obj = point.proceed();
		} catch (Exception e) {
			log.setType(LogTypeEnum.ERROR.getType());
			log.setException(e.getMessage());
			// 抛出异常
			throw e;
		} finally {
			// 计算方法执行耗时
			Long endTime = System.currentTimeMillis();
			log.setTime(endTime - startTime);
			// 保存日志
			saveLog(log);
		}
		return obj;
	}

	/**
	 * 异步保存日志
	 *
	 * @param log 日志信息
	 */
	@Async
	public void saveLog(top.blackr.folivora.common.log.aspect.SysLog log) {
		String ip = log.getRemoteAddr();
		if (StringUtil.isNotBlank(ip)) {
			String address = ip2regionSearcher.getAddress(ip);
			log.setAddressResource(address);
		}
		logService.save(log);
	}

	/**
	 * 获取spel 定义的参数值
	 *
	 * @param context 参数容器
	 * @param key     key
	 * @param clazz   需要返回的类型
	 * @param <T>     返回泛型
	 * @return 参数值
	 */
	private <T> T getValue(EvaluationContext context, String key, Class<T> clazz) {
		SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
		Expression expression = spelExpressionParser.parseExpression(key);
		return expression.getValue(context, clazz);
	}

	/**
	 * 获取参数容器
	 *
	 * @param arguments       方法的参数列表
	 * @param signatureMethod 被执行的方法体
	 * @return 装载参数的容器
	 */
	private EvaluationContext getContext(Object[] arguments, Method signatureMethod) {
		String[] parameterNames = new StandardReflectionParameterNameDiscoverer().getParameterNames(signatureMethod);
		EvaluationContext context = new StandardEvaluationContext();
		if (parameterNames == null) {
			return context;
		}
		for (int i = 0; i < arguments.length; i++) {
			context.setVariable(parameterNames[i], arguments[i]);
		}
		return context;
	}
}
